﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSharp.Core.DataTypes;

namespace SSharp.Core.Evaluator {
	public static partial class Eval {
		private class DefinitionEvaluator : SpecialFormEvaluator {
			private static Symbol defineSymbol = new Symbol("define");
			private static Symbol beginSymbol = new Symbol("begin");
			private static Symbol lambdaSymbol = new Symbol("lambda");
			private static Symbol renameSymbol = new Symbol("rename-lambda");
			public DefinitionEvaluator() : base("define") { }

			public override EvaluationResult Evaluate(object[] parameters, IEnvironment env) {
				if (parameters.Length < 2) {
					throw new SyntaxError("define: expects at least two parameters (what to the define and the value)");
				}

				object var = parameters[0];
				object value = List.Create(parameters.Skip(1));

				if (var is Symbol) {
					object result = evaluateAsValue(env, value);

					// simple definition - define the variable
					env.GetTopFrame()[(Symbol)var] = new Box(result);

					// let's have define return the variable name for now
					return new EvaluationResult(var);
				} else if (var is Cons) {
					Cons cons = (Cons)var;
					if (!(cons.car is Symbol)) {
						throw new SyntaxError("define: invalid definition syntax");
					}
					// get the name and parameters
					Symbol name = (Symbol)cons.car;
					object lambdaParameters = cons.cdr;

					// expand the definition syntax
					object expandedDefine = expandFunctionDefinition(name, lambdaParameters, value);

					// evaluate. This should define the procedure
					return new MoreEvaluationNeeded(expandedDefine, env);
				} else {
					throw new InvalidOperationException("define: invalid syntax");
				}
			}

			private static object evaluateAsValue(IEnvironment env, object value) {
				object result;
				object beginValue = new Cons(
								   beginSymbol,
								   value);
				result = Eval.Evaluate(beginValue, env);
				return result;
			}

			private static object expandFunctionDefinition(Symbol name, object parameters, object value) {
				object lambda = new Cons(
					lambdaSymbol,
					new Cons(
						parameters,
						value));

				object renamed = List.Create(
					renameSymbol,
					lambda,
					name.ToString());

				object expandedDefine = List.Create(
					defineSymbol,
					name,
					renamed);

				return expandedDefine;
			}
		}
	}
}
